﻿/*===========================================================================================
 * 
 *  Author          : Arturo Salvamante
 * 
 *  File            : PCSCItem.cs
 * 
 *  Copyright (C)   : Advanced Card System Ltd
 * 
 *  Description     : Contains Methods and Properties for PCSC
 * 
 *  Date            : June 03, 2011
 * 
 *  Revision Traile : [Author] / [Date if modification] / [Details of Modifications done]
 * 
 * 
 * =========================================================================================*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ACS
{
    public delegate void TransmitApduHandler(object sender, transmitApduEventArg e);

    public class transmitApduEventArg : EventArgs
    {
        private byte[] _data;
        public byte[] data
        {
            get { return _data; }
            set { _data = value; }
        }

        public transmitApduEventArg(byte[] data)
        {
            _data = data;
        }

        public string getAsString(bool spaceinBetween)
        {
            if (_data == null)
                return "";

            string tmpStr = string.Empty;
            for (int i = 0; i < _data.Length; i++)
            {
                tmpStr += string.Format("{0:X2}", _data[i]);

                if (spaceinBetween)
                    tmpStr += " ";
            }
            return tmpStr;
        }
    }

    public class PcscItem
    {
        private IntPtr hCard_ = new IntPtr();
        private IntPtr hContext_ = new IntPtr();
        private int pProtocol_ = PCSCProvider.SCARD_PROTOCOL_T0 | PCSCProvider.SCARD_PROTOCOL_T1;
        private int pdwActiveProtocol_;
        private int shareMode_ = PCSCProvider.SCARD_SHARE_SHARED;
        private string readerName_ = "";
        private int lastError = 0;

        public event TransmitApduHandler onSendCommand;
        public event TransmitApduHandler onReceivedCommand;

        public IntPtr cardHandle
        {
            get { return hCard_; }
            set { hCard_ = value; }
        }

        public IntPtr resourceMngrContext
        {
            get { return hContext_; }
            set { hContext_ = value; }
        }

        public int preferedProtocol
        {
            get { return pProtocol_; }
            set { pProtocol_ = value; }
        }

        public int activeProtocol
        {
            get { return pdwActiveProtocol_; }
            set { pdwActiveProtocol_ = value; }
        }

        public int shareMode
        {
            get { return shareMode_; }
            set { shareMode_ = value; }
        }

        public string readerName
        {
            get { return readerName_; }
            set { readerName_ = value; }
        }

        #region Private Methods

        public int establishContext()
        {   
            return  PCSCProvider.SCardEstablishContext(PCSCProvider.SCARD_SCOPE_USER, 0, 0, ref hContext_);
        }

        public int releaseContext()
        {
            return PCSCProvider.SCardReleaseContext(hContext_);       
        }

        #endregion

        public int connect()
        {
            if (readerName_.Trim() == "")
                throw new Exception("Smartacard reader is not specified");

            return connect(readerName_, pProtocol_, shareMode_);
        }

        public int connect(string readerName)
        {
            readerName_ = readerName;
            return connect(readerName_, pProtocol_, shareMode_);
        }

        public int connect(string readerName, int preferedProtocol, int shareMode)
        {
            int returnCode;

            returnCode = PCSCProvider.SCardConnect(hContext_, readerName, shareMode, preferedProtocol, ref hCard_, ref pdwActiveProtocol_);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
            {
                lastError = returnCode;
                return returnCode;
            }

            shareMode_ = shareMode;
            pProtocol_ = preferedProtocol;
            readerName_ = readerName;

            return returnCode;
        }
        
        public string[] getAllReaders()
        {
            byte[] returnData;
            byte[] sReaderGroup = null;
            string[] readerList = new string[0];
            string readerStr = string.Empty;
            int returnCode;
            IntPtr hContext = new IntPtr();
            int readerCount = 255;

            returnCode = PCSCProvider.SCardEstablishContext(PCSCProvider.SCARD_SCOPE_USER, 0, 0, ref hContext);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
            {
                lastError = returnCode;
                return readerList;
            }

            returnCode = PCSCProvider.SCardListReaders(hContext_, null, null, ref readerCount);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
            {
                lastError = returnCode;
                return readerList;
            }

            returnData = new byte[readerCount];

            returnCode = PCSCProvider.SCardListReaders(hContext_, sReaderGroup, returnData, ref readerCount);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
                if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
                    return readerList;


            readerStr = System.Text.ASCIIEncoding.ASCII.GetString(returnData).Trim('\0');
            readerList = readerStr.Split('\0');

            return readerList;
        }
        
        public int disconnect()
        {
            return PCSCProvider.SCardDisconnect(hCard_, PCSCProvider.SCARD_UNPOWER_CARD);
        }

        public int sendApdu(ref Apdu apdu)
        {
            byte[] sendBuff, recvBuff;
            int sendLen, recvLen, returnCode;

            PCSCProvider.SCARD_IO_REQUEST ioRequest;
            ioRequest.dwProtocol = pdwActiveProtocol_;
            ioRequest.cbPciLength = 8;

            if (apdu.sendData == null)
                sendBuff = new byte[5];
            else
                sendBuff = new byte[5 + apdu.sendData.Length];


            recvLen = apdu.lengthExpected + 2;


            Array.Copy(new byte[] { apdu.bCla, apdu.bIns, apdu.bP1, apdu.bP2, apdu.bP3 }, sendBuff, 5);

            if (apdu.sendData != null)
                Array.Copy(apdu.sendData, 0, sendBuff, 5, apdu.sendData.Length);

            sendLen = sendBuff.Length;

            apdu.sw = new byte[2];
            recvBuff = new byte[recvLen];

            sendCommandTriggerEvent(new transmitApduEventArg(sendBuff));
            returnCode = PCSCProvider.SCardTransmit(hCard_,
                                                ref ioRequest,
                                                sendBuff,
                                                sendLen,
                                                ref ioRequest,
                                                recvBuff,
                                                ref recvLen);
            if (returnCode == 0)
            {
                receivedCommandTriggerEvent(new transmitApduEventArg(recvBuff.Take(recvLen).ToArray()));
                if (recvLen > 1)
                    Array.Copy(recvBuff, recvLen - 2, apdu.sw, 0, 2);

                if (recvLen > 2)
                {
                    apdu.receiveData = new byte[recvLen - 2];
                    Array.Copy(recvBuff, 0, apdu.receiveData, 0, recvLen - 2);
                }
            }

            return returnCode;
        }

        public int sendCardControl(ref Apdu apdu, uint controlCode)
        {
            byte[] sendBuff, recvbuff;
            int sendLen, recvLen, returnCode, actualLength = 0;

            PCSCProvider.SCARD_IO_REQUEST ioRequest;
            ioRequest.dwProtocol = pdwActiveProtocol_;
            ioRequest.cbPciLength = 8;

            if (apdu.sendData == null)
                throw new Exception("No data specified");

            sendBuff = new byte[apdu.sendData.Length];
            recvLen = apdu.lengthExpected;

            Array.Copy(apdu.sendData, 0, sendBuff, 0, apdu.sendData.Length);

            sendLen = sendBuff.Length;

            apdu.sw = new byte[2];
            recvbuff = new byte[recvLen];

            sendCommandTriggerEvent(new transmitApduEventArg(sendBuff));
            returnCode = PCSCProvider.SCardControl(hCard_,
                                                controlCode,
                                                sendBuff,
                                                sendLen,
                                                recvbuff,
                                                recvbuff.Length,
                                                ref actualLength);

            if (returnCode == 0)
            {
                apdu.actualLengthReceived = actualLength;

                receivedCommandTriggerEvent(new transmitApduEventArg(recvbuff.Take(actualLength).ToArray()));

                apdu.receiveData = new byte[actualLength];
                Array.Copy(recvbuff, 0, apdu.receiveData, 0, actualLength);

                //if (actualLength > 1)
                //    Array.Copy(recvbuff, actualLength - 2, apdu.sw, 0, 2);

                //if (recvLen > 2)
                //{
                //    apdu.receiveData = new byte[actualLength - 2];
                //    Array.Copy(recvbuff, 0, apdu.receiveData, 0, actualLength - 2);
                //}
            }

            return returnCode;
        }
        
        void sendCommandTriggerEvent(transmitApduEventArg e)
        {
            if (onSendCommand == null)
                return;

            onSendCommand(this, e);
        }

        void receivedCommandTriggerEvent(transmitApduEventArg e)
        {
            if (onReceivedCommand == null)
                return;

            onReceivedCommand(this, e);
        }


    }
}
